/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#pragma once

#include "llvm_driver.h"
#include "../module.h"
#include "../sequence.h"
#include "../struct.h"

#include <type_traits>

namespace metal {
  namespace driver {
    template<typename Decls, typename Defs>
    class llvm_program;

    template<typename Decls, typename Defs>
    llvm_program<Decls, Defs> link(const module<Decls, Defs> &mod) {
      auto driver = new_ptr<llvm_driver>();

      mod.emit(*driver);

      return llvm_program<Decls, Defs>(driver);
    }

    template<typename S>
    struct signature_to_native_signature {
      typedef void type;
    };

    template<typename Ident, typename Decls>
    struct signature_to_signature { 
      typedef typename meta::get<Ident, Decls>::type signature;
      static_assert(!std::is_void<signature>::value, "function not defined");
      
      typedef typename signature_to_native_signature<signature>::type type;
    };

    template<typename T>
    struct return_type_to_native_return_type {
      typedef T type;
    };

    template<typename T>
    struct return_type_to_native_return_type<T*> {
      typedef sequence<T> type;
    };

    template<typename... Members>
    struct return_type_to_native_return_type<meta::structure<Members...> > {
      typedef typename trans_struct<Members...>::type spec;
      typedef struct_instance<spec> &&type;
    };

    template<typename ReturnType, typename ParamList>
    struct native_signature_to_function_pointer;

    template<typename ReturnType, typename... Params>
    struct native_signature_to_function_pointer<ReturnType, list<Params...> > {
      typedef ReturnType (*type)(Params...);
    };

    template<typename List, typename... Params>
    struct params_to_native_list;

    template<typename T>
    struct param_to_native {
      typedef T type;
    };

    template<typename... Members>
    struct param_to_native<meta::structure<Members...> > {
      typedef typename trans_struct<Members...>::type spec;
      typedef struct_instance<spec> &type;
    };

    template<typename... PackedParams, typename Param, typename... Params>
    struct params_to_native_list<list<PackedParams...>, Param, Params...> {
      typedef typename param_to_native<Param>::type NativeParam;
      typedef list<PackedParams..., NativeParam> this_list;
      typedef params_to_native_list<this_list, Params...> next;
      typedef typename next::type type;
    };

    template<typename List>
    struct params_to_native_list<List> {
      typedef List type;
    };

    template<typename ReturnType, typename... Params>
    struct signature_to_native_signature<
      list<
	ReturnType, Params...
	> 
      > {
      typedef typename return_type_to_native_return_type<ReturnType>::type 
      r_type;
      typedef typename params_to_native_list<list<>, Params...>::type p_types;

      typedef typename 
      native_signature_to_function_pointer<r_type, p_types>::type 
      type;
    };

    template<typename Decls, typename Defs>
    class llvm_program {
      typedef std::shared_ptr<llvm_driver> driver_ptr;
    public:
      llvm_program(driver_ptr driver)
	: driver(driver) {}

      template<typename I>
      typename signature_to_signature<I, Decls>::type
      call(expression<meta::ident_expr<I> > name) {
	typedef typename signature_to_signature<I, Decls>::type native_sig;

	static_assert(!std::is_void<native_sig>::value, 
		      "Could not create function-pointer from function "
		      " - implementation error");

	auto func_ptr = driver->get_function_pointer(name_of<I>());
	return reinterpret_cast<native_sig>(func_ptr);
      }
    private:
      const driver_ptr driver;
    };
  }
}
